8. 템플릿 메소드 패턴

  1. 8. 템플릿 메소드 패턴
    1. 템플릿 메소드 패턴(Template method pattern)
    2. 템플릿 메소드로 부터 무엇을 얻을 수 있었나요?
    3. 템플릿 메소드 패턴의 정의
    4. 템플릿 메소드와 후크(hook)
    5. 후크 활용
    6. 할리우드 원칙과 템플릿 메소드
    7. 템플릿 메소드를 이용해서 정렬하기
    8. 템플릿 메소드 핵심정리
    9. 문서에 대하여

알고리즘 캡슐화
알고리즘을 캡슐화하여 SubClass에서 필요할 때 언제든 가져다 쓸 수 있게 한다.

Coffee.java


public class Coffee {
	void prepareReceipe() {
		boilWater();
		brewCoffeeGrinds();
		pourInCup();
		addSugarAndMilk();
	}

	private void addSugarAndMilk() {
		System.out.println("설탕과 우유를 추가하는 중");
	}

	private void pourInCup() {
		System.out.println("컵에 따르는 중");
	}

	private void brewCoffeeGrinds() {
		System.out.println("필터를 통해서 커피를 우려내는 중");
	}

	private void boilWater() {
		System.out.println("물 끓이는 중");
	}
}


Tea.java


public class Tea {
	void prepareReceipe() {
		boilWater();
		steepTeaBag();
		pourInCup();
		addLemon();
	}

	private void addLemon() {
		System.out.println("레몬를 추가하는 중");
	}

	private void pourInCup() {
		System.out.println("컵에 따르는 중");
	}

	private void steepTeaBag() {
		System.out.println("차를 우려내는 중");
	}

	private void boilWater() {
		System.out.println("물 끓이는 중");
	}
}

BeverageTest.java


import org.junit.Test;

public class BeverageTest {
	
	@Test
	public void 커피_차() {
		Tea tea = new Tea();
		Coffee coffee = new Coffee();
		
		System.out.println("\nMaking tea...");
		tea.prepareReceipe();
		
		System.out.println("\nMaking coffee...");
		coffee.prepareReceipe();
	}
}

Coffee와 Tea에서 공통 메소드 추출하여 SuperClass(CaffeinBeverage)로 옮긴다.

CaffeinBeverage.java


public abstract class CaffeinBeverage {
	final void prepareReceipe() {
		boilWater();
		brew();
		pourInCup();
		addCondiments();
	}

	abstract void addCondiments();
	abstract void brew();

	private void pourInCup() {
		System.out.println("컵에 따르는 중");
	}

	private void boilWater() {
		System.out.println("물 끓이는 중");
	}

}

prepareReceipe()는 final로 선언하여 서브클래스에서 override해서 쓰지 못하게 했다.

Coffee.java


public class Coffee extends CaffeinBeverage {
	@Override
	void addCondiments() {
		System.out.println("설탕과 우유를 추가하는 중");
	}

	@Override
	void brew() {
		System.out.println("필터를 통해서 커피를 우려내는 중");
	}
}

Tea.java


public class Tea extends CaffeinBeverage {
	@Override
	void addCondiments() {
		System.out.println("레몬를 추가하는 중");
	}

	@Override
	void brew() {
		System.out.println("차를 우려내는 중");
	}
}

템플릿 메소드 패턴(Template method pattern)

CaffeinBeverage class에서 prepareReceipe() 메소드가 템플릿 메소드 이다.

  • 템플릿 메소드
    • 알고리즘의 각 단계(method)들을 정의
    • 그 중 한 개 이상의 단계(method)가 Subclass에 의해 제공될 수 있다.

템플릿 메소드로 부터 무엇을 얻을 수 있었나요?

처음 만들었던 Tea 및 Coffee 클래스템플릿 메소드를 적용한 Tea 및 Coffee 클래스
Coffee와 Tea 에서 각자 알고리즘 수행CaffeinBeverage 클래스에서 알고리즘을 독점
중복된 코드 존재Subclass에서 코드 재사용
알고리즘이 바뀌면 Subclass들을 함께 수정알고리즘이 한군데 모여 있으므로 그 부분만 수정
새로운 음료를 추가하려면 많은 작업 필요쉽게 추가 가능
알고리즘에 대한 지식과 구현이 여러 클래스에 분산알고리즘이 하나에 집중되어 있고 일부 구현만 Subclass에 의존

템플릿 메소드 패턴의 정의

  • 메소드에서 알고리즘의 골격 정의
  • 알고리즘의 여러 단계(method) 중 일부는 SubClass에서 구현 가능
  • 알고리즘의 구조는 그대로 유지하면서 SubClass에서 특정 단계를 재정의할 수 있다.

템플릿 메소드와 후크(hook)

후크(hook): 추상메소드에서 선언되는 메소드
기본적인 내용만 구현되어 있거나 아무 코드도 없는 것

CoffeeWithHook.java


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class CoffeeWithHook extends CaffeinBeverageWithHook {

	@Override
	void addCondiments() {
		System.out.println("우유와 설탕을 추가하는 중");
	}

	@Override
	void brew() {
		System.out.println("필터로 커피를 우려내는 중");
	}

	/*
	 * 후크 메소드: 서브클래스에서 재정의
	 */
	public boolean customerWantsCondiments() {
		String answer = getUserInput();
		
		if (answer.toLowerCase().startsWith("y"))
			return true;
		else
			return false;
	}

	private String getUserInput() {
		String answer = null;
		
		System.out.print("커피에 우유와 설탕을 넣어 드릴까요? (y/n) ");
		
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		try {
			answer = in.readLine();
		} catch(IOException ioe) {
			System.err.println("IO 오류");
		}
		
		if (answer == null) {
			return "no";
		}
		return answer;
	}
}

TeaWithHook.java


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class TeaWithHook extends CaffeinBeverageWithHook {

	@Override
	public void addCondiments() {
		System.out.println("레몬을 추가하는 중");
	}

	@Override
	public void brew() {
		System.out.println("차를 우려내는 중");
	}

	public boolean customerWantsCondiments() {
		String answer = getUserInfo();
		
		if (answer.toLowerCase().startsWith("y"))
			return true;
		else
			return false;
	}

	private String getUserInfo() {
		String answer = null;
		
		System.out.print("차에 레몬을 넣어 드릴까요? (y/n) ");
		
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		try {
			answer = in.readLine();
		} catch(IOException ioe) {
			System.err.println("IO 오류");
		}
		
		if (answer == null) {
			return "no";
		}
		return answer;
	}
}

BeverageTestDrive.java


public class BeverageTestDrive {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		TeaWithHook teaHook = new TeaWithHook();
		CoffeeWithHook coffeeHook = new CoffeeWithHook();
		
		System.out.println("\n차 준비중...");
		teaHook.prepareReceipe();
		
		System.out.println("\n커피 준비중...");
		coffeeHook.prepareReceipe();
	}

}

후크 활용

후크를 사용하려면 SubClass에서 Override해야 한다.
당연히 추상클래스에서 구현한 기본적인 내용을 쓰려면 Override하지 않으면 그만이다.

Q & A

Q. 템플릿을 만들 때 언제 추상 메소드를 쓰고 언제 후크를 써야 하나요?
A. 후크: 알고리즘의 특정 부분이 선택적으로 사용될 때
추상 메소드는 알고리즘의 특정 단계만 제공하고 SubClass에서 구현해야 할 때

할리우드 원칙과 템플릿 메소드

할리우드 원칙

먼저 연락하지 마세요. 저희가 연락 드리겠습니다.

템플릿 메소드를 이용해서 정렬하기

Arrays 클래스의 정렬 메소드 살펴보기


public static void sort(Object[] a) {
	Object aux[] = (Object[]) a.clone();
	mergeSort(aux, a, 0, a.length, 0);
}

private static void mergeSort(Object src[], Object desc[], int low, int high, int off) {
	for (int i=low; i<high; i++) {
		for (int j=i; j>low && ((Comparable)desc[j-1]).compareTo((Comparable)dest[j]>0;j--) {
			swap(desc, j, j-1);
		}
	}
	return;
}

오리를 정렬해야 해요

  • Arrays에 있는 정렬용 템플릿 메소드에서 알고리즘을 제공한다.
  • 하지만, compareTo() 메소드를 구현해서 오리를 비교하는 방법을 알려줘야 한다.

    이상한데요??
    서브 클래스를 만들어야 하지 않나요?
    템플릿 메소드 패턴은 일부 단계를 서브 클래스에서 구현한다고 했잖아요.
    Arrays의 서브 클래스를 만들 수 없을텐데 어떻게 sort()를 사용할 수 있죠?

    • sort()
      • 정적 메소드(슈퍼 클래스에 들어있는 것처럼 생각하면 됨)
      • 모든 배열에서 그 메소드를 쓸 수 있다.(Object[])
      • 정렬 알고리즘인 compareTo() 를 구현해야 한다.

compareTo()

두 객체를 비교해서 그 대소 관계를 판단한 결과를 리턴하는 메소드

Duck 객체 대소 비교

Duck.java


public class Duck implements Comparable {
	String name;
	int weight;
	
	public Duck(String name, int weight) {
		this.name = name;
		this.weight = weight;
	}
	
	public String toString() {
		return name + " weight " + weight;
	}
	
	public int compareTo(Object object) {
		Duck otherDuck = (Duck) object;
		
		if (this.weight < otherDuck.weight)
			return -1;
		else if (this.weight == otherDuck.weight)
			return 0;
		else	//this.weight > otherDuck.weight
			return 1;
	}

}

DuckSortTest.java


import java.util.Arrays;

import org.junit.Test;


public class DuckSortTest {
	@Test
	public void sort테스트() {
		Duck[] ducks = {
				new Duck("Daffy", 8),
				new Duck("Dewey", 2),
				new Duck("Howard", 7),
				new Duck("Louie", 2),
				new Duck("Donald", 10),
				new Duck("Huey", 2)
		};
		
		System.out.println("Before sorting: ");
		display(ducks);
		
		Arrays.sort(ducks);
		
		System.out.println("\nAfter sortingL ");
		display(ducks);
		
	}

	private void display(Duck[] ducks) {
		for (int i = 0; i < ducks.length; i++) {
			System.out.println(ducks[i]);
		}
	}
}


템플릿 메소드 핵심정리

  • 알고리즘의 단계를 정의하는 데 일부 단계는 서브클래스에서 구현하도록 할 수 있다.
  • 코드 재사용
  • 템플릿 메소드가 들어있는 추상클래스는 구상 메소드, 추상 메소드, 후크를 정의할 수 있다.
  • 후크(hook)는 아무 일도 하지 않거나 기본 행동을 정의하는 메소드로 서브 클래스에서 오버라이드 할 수 있다.
  • 서브 클래스에서 함부로 알고리즘을 고치지 못하게 할려면 final로 선언해라
  • 헐리우드 원칙에 의하면 저수준 모듈을 언제 어떻게 호출할 지는 고수준 모듈에서 결정하는 것이 좋다.
  • 스트래티지 패턴과 템플릿 페턴은 모두 알고리즘을 캡슐화한다.
    전자는 상속을 후자는 구성을 이용한다.
  • 팩토리 메소드 패턴은 특화된 템플릿 메소드이다.

문서에 대하여